############################################################################
# 
# RSS generator
#
# Copyright (c) 2013, Alexander Galanin <al@galanin.nnov.ru>
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 
# * Redistributions of source code must retain the above copyright notice,
#   this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
#   notice, this list of conditions and the following disclaimer in the
#   documentation and/or other materials provided with the distribution.
# * Neither the name of Alexander Galanin nor the names of its contributors
#   may be used to endorse or promote products derived from this software
#   without specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL Alexander Galanin BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
############################################################################

package require Tcl 8.5
package require struct::list
package require struct::set

package provide rss 1.0

namespace eval rss {

namespace export \
    init \
    requiredFields \
    begin \
    addItem \
    end
namespace ensemble create

variable requiredFields {link title date description}

variable generator {rss.tcl}
variable handleIndex 0

# Set generator field
proc init {_generator} {
    variable generator $_generator
}

# Generate CDATA section from string
proc cdata {string} {
    set prefix {<![CDATA[}
    set suffix {]]>}
    # break all CDATA end tags
    regsub -all $suffix $string "]]$suffix$prefix>" string
    return "$prefix$string$suffix"
}

# Format date in RFC 2822 format
proc formatDate {seconds} {
    clock format $seconds -format {%a, %d %b %Y %H:%M:%S %z}
}

# Get list of fields required for addItem proc
proc requiredFields {} {
    variable requiredFields
    return $requiredFields
}

# Begin RSS feed
# @param link link field
# @param title title field
# @param description description field
# @return rss data handle
proc begin {link title description} {
    variable generator
    variable handleIndex

    set handle [namespace current]::rss[incr handleIndex]
    set $handle [subst {<?xml version="1.0" encoding="UTF-8"?>
        <rss version="2.0">
            <channel>
                <title>[cdata $title]</title>
                <link>[cdata $link]</link>
                <description>[cdata $description]</description>
                <lastBuildDate>[formatDate [clock seconds]]</lastBuildDate>
                <generator>[cdata $generator]</generator>
    }]
    return $handle
}

# Add item tag to RSS feed
# @param handle rss data handle
proc addItem {handle args} {
    variable requiredFields
    set diff [struct::set difference $requiredFields [dict keys $args]]
    if {![struct::set empty $diff]} {
        error "not all required fields are set ({$diff} is missing)"
    }
    set categories {}
    dict with args {
        append $handle [subst {
                <item>
                  <guid isPermaLink='true'>[cdata $link]</guid>
                  <pubDate>[formatDate $date]</pubDate>
                  <title>[cdata $title]</title>
                  <link>[cdata $link]</link>
                  <description>[cdata $description]</description>
                  <comments>[cdata $link]</comments>
                  [join [struct::list mapfor category $categories {
                      subst {<category>[cdata $category]</category>}
                  }]]
                </item>
        }]
    }
}

# Close opened tags, return value and unset variable
proc end {handle} {
    set res [append $handle [subst {
            </channel>
        </rss>
    }]]
    unset $handle
    return $res
}

}
